﻿using UnityEngine;
using System.Collections;
using System.Threading;

namespace Skill.Framework
{
    public static class TextureUtility
    {
        #region Rotation
        public static Texture2D Rotate90(Texture2D texture)
        {
            Texture2D virtualPhoto = new Texture2D(texture.height, texture.width, TextureFormat.RGB24, false);
            Color[] origPixels = texture.GetPixels();
            Color[] rotPixels = new Color[origPixels.Length];

            Rotate90(origPixels, rotPixels, texture.width, texture.height);

            virtualPhoto.SetPixels(rotPixels);
            virtualPhoto.Apply();
            return virtualPhoto;
        }

        public static Texture2D Rotate180(Texture2D texture)
        {
            Texture2D virtualPhoto = new Texture2D(texture.height, texture.width, TextureFormat.RGB24, false);
            Color[] origPixels = texture.GetPixels();
            Color[] rotPixels = new Color[origPixels.Length];

            Rotate90(origPixels, rotPixels, texture.width, texture.height);
            Rotate90(rotPixels, origPixels, texture.height, texture.width);

            virtualPhoto.SetPixels(origPixels);
            virtualPhoto.Apply();
            return virtualPhoto;
        }

        private static void Rotate90(Color[] origPixels, Color[] rotPixels, int width, int height)
        {
            for (var x = 0; x < width; x++)
            {
                for (var y = 0; y < height; y++)
                {
                    rotPixels[x * height + y] = origPixels[(y * width) + (width - 1 - x)];
                }
            }
        }

        public static Color[] Rotate90(Color[] origPixels, int width, int height)
        {
            Color[] rotPixels = new Color[origPixels.Length];
            for (var x = 0; x < width; x++)
            {
                for (var y = 0; y < height; y++)
                {
                    rotPixels[x * height + y] = origPixels[(y * width) + (width - 1 - x)];
                }
            }
            return rotPixels;
        } 
        #endregion

        #region Scale

        private class ThreadData
        {
            public int start;
            public int end;
            public ThreadData(int s, int e)
            {
                start = s;
                end = e;
            }
        }

        private static Color[] texColors;
        private static Color[] newColors;
        private static int w;
        private static float ratioX;
        private static float ratioY;
        private static int w2;
        private static int finishCount;
        private static Mutex mutex;

        public static int DesiredCores { get; set; }

        /// <summary>
        /// Scale texture using point
        /// </summary>
        /// <param name="texture">
        /// texture to scale
        /// Only works on ARGB32, RGB24 and Alpha8 textures that are marked readable
        /// </param>
        /// <param name="newWidth">new width</param>
        /// <param name="newHeight">new height</param>
        public static void ScalePoint(Texture2D texture, int newWidth, int newHeight)
        {
            ThreadedScale(texture, newWidth, newHeight, false);
        }

        /// <summary>
        /// Scale texture using bilinear
        /// </summary>
        /// <param name="texture">
        /// texture to scale
        /// Only works on ARGB32, RGB24 and Alpha8 textures that are marked readable
        /// </param>
        /// <param name="newWidth">new width</param>
        /// <param name="newHeight">new height</param>
        public static void ScaleBilinear(Texture2D texture, int newWidth, int newHeight)
        {
            ThreadedScale(texture, newWidth, newHeight, true);
        }

        private static void ThreadedScale(Texture2D tex, int newWidth, int newHeight, bool useBilinear)
        {
            texColors = tex.GetPixels();
            newColors = new Color[newWidth * newHeight];
            if (useBilinear)
            {
                ratioX = 1.0f / ((float)newWidth / (tex.width - 1));
                ratioY = 1.0f / ((float)newHeight / (tex.height - 1));
            }
            else
            {
                ratioX = ((float)tex.width) / newWidth;
                ratioY = ((float)tex.height) / newHeight;
            }
            w = tex.width;
            w2 = newWidth;
            var cores = Mathf.Min(1, DesiredCores, Mathf.Min(SystemInfo.processorCount, newHeight));
            var slice = newHeight / cores;



            finishCount = 0;
            if (mutex == null)
            {
                mutex = new Mutex(false);
            }
            if (cores > 1)
            {
                int i = 0;
                ThreadData threadData;
                for (i = 0; i < cores - 1; i++)
                {
                    threadData = new ThreadData(slice * i, slice * (i + 1));
                    ParameterizedThreadStart ts = useBilinear ? new ParameterizedThreadStart(BilinearScale) : new ParameterizedThreadStart(PointScale);
                    Thread thread = new Thread(ts);
                    thread.Start(threadData);
                }
                threadData = new ThreadData(slice * i, newHeight);
                if (useBilinear)
                {
                    BilinearScale(threadData);
                }
                else
                {
                    PointScale(threadData);
                }
                while (finishCount < cores)
                {
                    Thread.Sleep(1);
                }
            }
            else
            {
                ThreadData threadData = new ThreadData(0, newHeight);
                if (useBilinear)
                {
                    BilinearScale(threadData);
                }
                else
                {
                    PointScale(threadData);
                }
            }

            tex.Resize(newWidth, newHeight);
            tex.SetPixels(newColors);
            tex.Apply();
        }

        public static void BilinearScale(System.Object obj)
        {
            ThreadData threadData = (ThreadData)obj;
            for (var y = threadData.start; y < threadData.end; y++)
            {
                int yFloor = (int)Mathf.Floor(y * ratioY);
                var y1 = yFloor * w;
                var y2 = (yFloor + 1) * w;
                var yw = y * w2;

                for (var x = 0; x < w2; x++)
                {
                    int xFloor = (int)Mathf.Floor(x * ratioX);
                    var xLerp = x * ratioX - xFloor;
                    newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp),
                                                           ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp),
                                                           y * ratioY - yFloor);
                }
            }

            mutex.WaitOne();
            finishCount++;
            mutex.ReleaseMutex();
        }

        public static void PointScale(System.Object obj)
        {
            ThreadData threadData = (ThreadData)obj;
            for (var y = threadData.start; y < threadData.end; y++)
            {
                var thisY = (int)(ratioY * y) * w;
                var yw = y * w2;
                for (var x = 0; x < w2; x++)
                {
                    newColors[yw + x] = texColors[(int)(thisY + ratioX * x)];
                }
            }

            mutex.WaitOne();
            finishCount++;
            mutex.ReleaseMutex();
        }

        private static Color ColorLerpUnclamped(Color c1, Color c2, float value)
        {
            return new Color(c1.r + (c2.r - c1.r) * value,
                              c1.g + (c2.g - c1.g) * value,
                              c1.b + (c2.b - c1.b) * value,
                              c1.a + (c2.a - c1.a) * value);
        }

        #endregion
    }
}